﻿/*
 * ArcMath
 * 
 * initial implementation : 31.8.2007
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections.Generic;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;


namespace Petra.Gis.Geometry.Utils
{
  /// <summary>
  /// supports 2d simple 72 point polyg. arc stroke
  /// </summary>
  public static class ArcMath
  {

    private struct CircleStrokePoint
    {
      public double X;
      public double Y;
      public int angle;
    }

    private static CircleStrokePoint[] oStrokeArray; //might be linestring!!

    static ArcMath()
    {
      InitCircleArray();
    }

    /*
    private Coordinate getCircleCenter(Arc geom)
    {

      double dx, dy;
      double s;
      double kx, ky;
      double dReducedRadius;

      ReadonlyCoordinate tmpPoint = new ReadonlyCoordinate(geom);
      //todo:resolve normal vector !!
      dx = (geom.coordList.endPoint.X - geom.coordList.endPoint.X);
      dy = (geom.coordList.endPoint.Y - geom.coordList.endPoint.Y);
      s = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
      dReducedRadius = Math.Sqrt(Math.Pow(geom.radius, 2) - (Math.Pow(s / 2, 2)));
      //If dNormalVector(2) < 0 Then
      //    dReducedRadius = (-1) * dReducedRadius
      //End If
      kx = dx / s;
      ky = dy / s;

      tmpPoint.dX = StartPoint.X + 0.5 * s * kx - dReducedRadius * ky;
      tmpPoint.dY = StartPoint.Y + 0.5 * s * ky + dReducedRadius * kx;
      tmpPoint.dZ = 0;

      return tmpPoint;
    }
    */




    public static Coordinate GetCircleCenter(ICoordinate StartPoint, ICoordinate EndPoint, double radius, IVector NormalVector)
    {

      double dx, dy;
      double s;
      double kx, ky;
      double dReducedRadius;

      Coordinate tmpPoint = new Coordinate();
      //todo:resolve normal vector !!
      dx = (EndPoint.X - StartPoint.X);
      dy = (EndPoint.Y - StartPoint.Y);
      s = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
      dReducedRadius = Math.Sqrt(Math.Pow(radius, 2) - (Math.Pow(s / 2, 2)));
      /*If dNormalVector(2) < 0 Then
          dReducedRadius = (-1) * dReducedRadius
      End If*/
      kx = dx / s;
      ky = dy / s;

      tmpPoint.X = StartPoint.X + 0.5 * s * kx - dReducedRadius * ky;
      tmpPoint.Y = StartPoint.Y + 0.5 * s * ky + dReducedRadius * kx;
      tmpPoint.Z = 0;

      return tmpPoint;
    }




    /// <summary>
    /// returns clockwise measured angle of given vector in Degrees
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    public static double GetAngleByVector(double x, double y)
    {

      if (x == 0 && y == 0)
        throw new ArgumentException("invalid input vector", "x = 0 and y = 0");


      double ang = Math.Atan(y / x);

      if (ang < 0)
        ang *= -1;

      if (x > 0 && y > 0)				 // 1rst quadrant
      {
        //ang = ang;
      }
      else if (x < 0 && y > 0)   // 2nd quadrant
      {
        ang = Math.PI - ang;
      }
      else if (x < 0 && y < 0)   // 3rd quadrant
      {
        ang = Math.PI + ang;
      }
      else									 // 4th quadrant
      {
        ang = (2.0 * Math.PI) - ang;
      }

      return ang * 180 / Math.PI;

    }





    public static double GetAngleBetweenVectors(double x1, double y1, double x2, double y2)
    {
      return GetAngleByVector(x1, y1) - GetAngleByVector(x2, y2);
    }





    public static LineString getStrokedArc(Arc geom)
    {
      if (geom.coordList.startPoint.Z != geom.coordList.endPoint.Z || Math.Abs(geom.normalVector.K) != 1)
          throw new ApplicationException("3d circle stroke not supported");

        LineString tmpLString = new LineString();
        tmpLString.coordList = getStrokedArc2d(geom.coordList.startPoint, geom.coordList.endPoint, geom.centerPoint, geom.normalVector, geom.radius);
        return tmpLString;
    }




    private static CoordinateList getStrokedArc2d(ICoordinate oStartPoint, ICoordinate oEndPoint, ICoordinate CenterPoint, IVector NormalVector, double Radius)
    {
      double X;
      double Y;
      int lStartIndex;
      int lEndIndex;
      int lCircleIndex;
      int lNumPoints;
      int lIndex;

      Coordinate StartPoint = new Coordinate();
      Coordinate EndPoint = new Coordinate();

      Radius = Math.Abs(Radius);

      if (NormalVector.K < 0)
      {
        //flip start and end point
        //circle stroke array is clockwise
        StartPoint.X = oEndPoint.X;
        StartPoint.Y = oEndPoint.Y;
        EndPoint.X = oStartPoint.X;
        EndPoint.Y = oStartPoint.Y;
      }
      else
      {
        //just copy readonly points into readwrite local
        StartPoint.X = oStartPoint.X;
        StartPoint.Y = oStartPoint.Y;
        EndPoint.X = oEndPoint.X;
        EndPoint.Y = oEndPoint.Y;
      }

      X = (StartPoint.X - CenterPoint.X) / Radius;
      Y = (StartPoint.Y - CenterPoint.Y) / Radius;
      lStartIndex = GetAngleIndex(X, Y) + 1;

      X = (EndPoint.X - CenterPoint.X) / Radius;
      Y = (EndPoint.Y - CenterPoint.Y) / Radius;
      lEndIndex = GetAngleIndex(X, Y);

      if (lEndIndex < lStartIndex)
      {
        lNumPoints = 71 - lStartIndex + 3;
        lNumPoints = lNumPoints + lEndIndex;
      }
      else
        lNumPoints = lEndIndex - lStartIndex + 3;

      lNumPoints = lNumPoints + 1;

      //prepare coordlist for stroke points
      CoordinateList tmpCoordinateList = new CoordinateList(lNumPoints, Dimension.dim2D);

      /*
      mvarUboundOrig = lNumPoints + 1
      On Error Resume Next
      mvarUboundOut = UBound(RetArray)
      If Err <> 0 Then
        On Error GoTo 0
        newArraySize = mvarUboundOrig - 1
        ReDim RetArray(0 To newArraySize)
      Else
        newArraySize = mvarUboundOut + mvarUboundOrig
        ArrayOffset = mvarUboundOut + 1
        ReDim Preserve RetArray(0 To newArraySize)
      End If
      */

      //first point is start point
      lCircleIndex = lStartIndex;
      tmpCoordinateList.startPoint.X = StartPoint.X;
      tmpCoordinateList.startPoint.Y = StartPoint.Y;

      for (lIndex = 1; lIndex <= (lNumPoints - 2); lIndex++)
      {
        if ((lEndIndex < lStartIndex) && (lCircleIndex > 71))
          lCircleIndex = 0;

        tmpCoordinateList[lIndex].X = (oStrokeArray[lCircleIndex].X * Radius) + CenterPoint.X;
        tmpCoordinateList[lIndex].Y = (oStrokeArray[lCircleIndex].Y * Radius) + CenterPoint.Y;
        lCircleIndex = lCircleIndex + 1;
      }

      lIndex = lNumPoints - 1; //last point is end point
      tmpCoordinateList[lIndex].X = EndPoint.X;
      tmpCoordinateList[lIndex].Y = EndPoint.Y;

      if (NormalVector.K < 0)
      {
        //reverse whole array couter-clockwise
        lCircleIndex = (int)Math.Floor((double)(lNumPoints / 2));
        lCircleIndex = lCircleIndex - 1;
        lEndIndex = lNumPoints - 1;
        for (lIndex = 0; lIndex <= lCircleIndex; lIndex++)
        {
          X = tmpCoordinateList[lIndex].X;
          Y = tmpCoordinateList[lIndex].Y;
          tmpCoordinateList[lIndex].X = tmpCoordinateList[lEndIndex - lIndex].X;
          tmpCoordinateList[lIndex].Y = tmpCoordinateList[lEndIndex - lIndex].Y;
          tmpCoordinateList[lEndIndex - lIndex].X = X;
          tmpCoordinateList[lEndIndex - lIndex].Y = Y;
        }
      }
      return tmpCoordinateList;
    }




    private static int GetAngleIndex(double X, double Y)
    {
      /***************************************************************
      *  X and Y from range <0;1>
      *  function returns angle lIndex smaller then X,Y value
      ***************************************************************/
      int Angle;

      if (X > -0.0038 && Y >= -0.0038)
      {// I quadrant
        for (Angle = 0; Angle <= 18; Angle++)
        {
          if ((X - 0.0019) > oStrokeArray[Angle].X)
            return Angle - 1;
        }
      }
      if (X <= -0.0038 && Y > -0.0038)
      {// II quadrant
        for (Angle = 18; Angle <= 36; Angle++)
        {
          if ((Y - 0.0019) > oStrokeArray[Angle].Y)
            return Angle - 1;
        }
      }
      if (X < -0.0038 && Y <= -0.0038)
      {// III quadrant
        for (Angle = 36; Angle <= 54; Angle++)
        {
          if ((X + 0.0019) < oStrokeArray[Angle].X)
            if (Angle > 36)
              return Angle - 1;
            else
              return Angle;
        }
      }
      if (X >= -0.0038 && Y < -0.0038)
      { // IV quadrant
        for (Angle = 54; Angle <= 72; Angle++)
        {
          if ((Y + 0.0019) < oStrokeArray[Angle].Y)
            return Angle - 1;
        }
      }
      return -1;
    }




    private static void InitCircleArray()
    {
      oStrokeArray = new CircleStrokePoint[72];
      oStrokeArray[0].X = 1;
      oStrokeArray[0].Y = 0;
      oStrokeArray[0].angle = 0;
      oStrokeArray[1].X = 0.996194698;
      oStrokeArray[1].Y = 0.087155743;
      oStrokeArray[1].angle = 5;
      oStrokeArray[2].X = 0.984807753;
      oStrokeArray[2].Y = 0.173648178;
      oStrokeArray[2].angle = 10;
      oStrokeArray[3].X = 0.965925826;
      oStrokeArray[3].Y = 0.258819045;
      oStrokeArray[3].angle = 15;
      oStrokeArray[4].X = 0.939692621;
      oStrokeArray[4].Y = 0.342020143;
      oStrokeArray[4].angle = 20;
      oStrokeArray[5].X = 0.906307787;
      oStrokeArray[5].Y = 0.422618262;
      oStrokeArray[5].angle = 25;
      oStrokeArray[6].X = 0.866025404;
      oStrokeArray[6].Y = 0.5;
      oStrokeArray[6].angle = 30;
      oStrokeArray[7].X = 0.819152044;
      oStrokeArray[7].Y = 0.573576436;
      oStrokeArray[7].angle = 35;
      oStrokeArray[8].X = 0.766044443;
      oStrokeArray[8].Y = 0.64278761;
      oStrokeArray[8].angle = 40;
      oStrokeArray[9].X = 0.707106781;
      oStrokeArray[9].Y = 0.707106781;
      oStrokeArray[9].angle = 45;
      oStrokeArray[10].X = 0.64278761;
      oStrokeArray[10].Y = 0.766044443;
      oStrokeArray[10].angle = 50;
      oStrokeArray[11].X = 0.573576436;
      oStrokeArray[11].Y = 0.819152044;
      oStrokeArray[11].angle = 55;
      oStrokeArray[12].X = 0.5;
      oStrokeArray[12].Y = 0.866025404;
      oStrokeArray[12].angle = 60;
      oStrokeArray[13].X = 0.422618262;
      oStrokeArray[13].Y = 0.906307787;
      oStrokeArray[13].angle = 65;
      oStrokeArray[14].X = 0.342020143;
      oStrokeArray[14].Y = 0.939692621;
      oStrokeArray[14].angle = 70;
      oStrokeArray[15].X = 0.258819045;
      oStrokeArray[15].Y = 0.965925826;
      oStrokeArray[15].angle = 75;
      oStrokeArray[16].X = 0.173648178;
      oStrokeArray[16].Y = 0.984807753;
      oStrokeArray[16].angle = 80;
      oStrokeArray[17].X = 0.087155743;
      oStrokeArray[17].Y = 0.996194698;
      oStrokeArray[17].angle = 85;
      oStrokeArray[18].X = 0;
      oStrokeArray[18].Y = 1;
      oStrokeArray[18].angle = 90;
      oStrokeArray[19].X = -0.087155743;
      oStrokeArray[19].Y = 0.996194698;
      oStrokeArray[19].angle = 95;
      oStrokeArray[20].X = -0.173648178;
      oStrokeArray[20].Y = 0.984807753;
      oStrokeArray[20].angle = 100;
      oStrokeArray[21].X = -0.258819045;
      oStrokeArray[21].Y = 0.965925826;
      oStrokeArray[21].angle = 105;
      oStrokeArray[22].X = -0.342020143;
      oStrokeArray[22].Y = 0.939692621;
      oStrokeArray[22].angle = 110;
      oStrokeArray[23].X = -0.422618262;
      oStrokeArray[23].Y = 0.906307787;
      oStrokeArray[23].angle = 115;
      oStrokeArray[24].X = -0.5;
      oStrokeArray[24].Y = 0.866025404;
      oStrokeArray[24].angle = 120;
      oStrokeArray[25].X = -0.573576436;
      oStrokeArray[25].Y = 0.819152044;
      oStrokeArray[25].angle = 125;
      oStrokeArray[26].X = -0.64278761;
      oStrokeArray[26].Y = 0.766044443;
      oStrokeArray[26].angle = 130;
      oStrokeArray[27].X = -0.707106781;
      oStrokeArray[27].Y = 0.707106781;
      oStrokeArray[27].angle = 135;
      oStrokeArray[28].X = -0.766044443;
      oStrokeArray[28].Y = 0.64278761;
      oStrokeArray[28].angle = 140;
      oStrokeArray[29].X = -0.819152044;
      oStrokeArray[29].Y = 0.573576436;
      oStrokeArray[29].angle = 145;
      oStrokeArray[30].X = -0.866025404;
      oStrokeArray[30].Y = 0.5;
      oStrokeArray[30].angle = 150;
      oStrokeArray[31].X = -0.906307787;
      oStrokeArray[31].Y = 0.422618262;
      oStrokeArray[31].angle = 155;
      oStrokeArray[32].X = -0.939692621;
      oStrokeArray[32].Y = 0.342020143;
      oStrokeArray[32].angle = 160;
      oStrokeArray[33].X = -0.965925826;
      oStrokeArray[33].Y = 0.258819045;
      oStrokeArray[33].angle = 165;
      oStrokeArray[34].X = -0.984807753;
      oStrokeArray[34].Y = 0.173648178;
      oStrokeArray[34].angle = 170;
      oStrokeArray[35].X = -0.996194698;
      oStrokeArray[35].Y = 0.087155743;
      oStrokeArray[35].angle = 175;
      oStrokeArray[36].X = -1;
      oStrokeArray[36].Y = 0;
      oStrokeArray[36].angle = 180;
      oStrokeArray[37].X = -0.996194698;
      oStrokeArray[37].Y = -0.087155743;
      oStrokeArray[37].angle = 185;
      oStrokeArray[38].X = -0.984807753;
      oStrokeArray[38].Y = -0.173648178;
      oStrokeArray[38].angle = 190;
      oStrokeArray[39].X = -0.965925826;
      oStrokeArray[39].Y = -0.258819045;
      oStrokeArray[39].angle = 195;
      oStrokeArray[40].X = -0.939692621;
      oStrokeArray[40].Y = -0.342020143;
      oStrokeArray[40].angle = 200;
      oStrokeArray[41].X = -0.906307787;
      oStrokeArray[41].Y = -0.422618262;
      oStrokeArray[41].angle = 205;
      oStrokeArray[42].X = -0.866025404;
      oStrokeArray[42].Y = -0.5;
      oStrokeArray[42].angle = 210;
      oStrokeArray[43].X = -0.819152044;
      oStrokeArray[43].Y = -0.573576436;
      oStrokeArray[43].angle = 215;
      oStrokeArray[44].X = -0.766044443;
      oStrokeArray[44].Y = -0.64278761;
      oStrokeArray[44].angle = 220;
      oStrokeArray[45].X = -0.707106781;
      oStrokeArray[45].Y = -0.707106781;
      oStrokeArray[45].angle = 225;
      oStrokeArray[46].X = -0.64278761;
      oStrokeArray[46].Y = -0.766044443;
      oStrokeArray[46].angle = 230;
      oStrokeArray[47].X = -0.573576436;
      oStrokeArray[47].Y = -0.819152044;
      oStrokeArray[47].angle = 235;
      oStrokeArray[48].X = -0.5;
      oStrokeArray[48].Y = -0.866025404;
      oStrokeArray[48].angle = 240;
      oStrokeArray[49].X = -0.422618262;
      oStrokeArray[49].Y = -0.906307787;
      oStrokeArray[49].angle = 245;
      oStrokeArray[50].X = -0.342020143;
      oStrokeArray[50].Y = -0.939692621;
      oStrokeArray[50].angle = 250;
      oStrokeArray[51].X = -0.258819045;
      oStrokeArray[51].Y = -0.965925826;
      oStrokeArray[51].angle = 255;
      oStrokeArray[52].X = -0.173648178;
      oStrokeArray[52].Y = -0.984807753;
      oStrokeArray[52].angle = 260;
      oStrokeArray[53].X = -0.087155743;
      oStrokeArray[53].Y = -0.996194698;
      oStrokeArray[53].angle = 265;
      oStrokeArray[54].X = 0;
      oStrokeArray[54].Y = -1;
      oStrokeArray[54].angle = 270;
      oStrokeArray[55].X = 0.087155743;
      oStrokeArray[55].Y = -0.996194698;
      oStrokeArray[55].angle = 275;
      oStrokeArray[56].X = 0.173648178;
      oStrokeArray[56].Y = -0.984807753;
      oStrokeArray[56].angle = 280;
      oStrokeArray[57].X = 0.258819045;
      oStrokeArray[57].Y = -0.965925826;
      oStrokeArray[57].angle = 285;
      oStrokeArray[58].X = 0.342020143;
      oStrokeArray[58].Y = -0.939692621;
      oStrokeArray[58].angle = 290;
      oStrokeArray[59].X = 0.422618262;
      oStrokeArray[59].Y = -0.906307787;
      oStrokeArray[59].angle = 295;
      oStrokeArray[60].X = 0.5;
      oStrokeArray[60].Y = -0.866025404;
      oStrokeArray[60].angle = 300;
      oStrokeArray[61].X = 0.573576436;
      oStrokeArray[61].Y = -0.819152044;
      oStrokeArray[61].angle = 305;
      oStrokeArray[62].X = 0.64278761;
      oStrokeArray[62].Y = -0.766044443;
      oStrokeArray[62].angle = 310;
      oStrokeArray[63].X = 0.707106781;
      oStrokeArray[63].Y = -0.707106781;
      oStrokeArray[63].angle = 315;
      oStrokeArray[64].X = 0.766044443;
      oStrokeArray[64].Y = -0.64278761;
      oStrokeArray[64].angle = 320;
      oStrokeArray[65].X = 0.819152044;
      oStrokeArray[65].Y = -0.573576436;
      oStrokeArray[65].angle = 325;
      oStrokeArray[66].X = 0.866025404;
      oStrokeArray[66].Y = -0.5;
      oStrokeArray[66].angle = 330;
      oStrokeArray[67].X = 0.906307787;
      oStrokeArray[67].Y = -0.422618262;
      oStrokeArray[67].angle = 335;
      oStrokeArray[68].X = 0.939692621;
      oStrokeArray[68].Y = -0.342020143;
      oStrokeArray[68].angle = 340;
      oStrokeArray[69].X = 0.965925826;
      oStrokeArray[69].Y = -0.258819045;
      oStrokeArray[69].angle = 345;
      oStrokeArray[70].X = 0.984807753;
      oStrokeArray[70].Y = -0.173648178;
      oStrokeArray[70].angle = 350;
      oStrokeArray[71].X = 0.996194698;
      oStrokeArray[71].Y = -0.087155743;
      oStrokeArray[71].angle = 355;
      oStrokeArray[72].X = 1;
      oStrokeArray[72].Y = 0;
      oStrokeArray[72].angle = 360;
    }

  }

}
